﻿using System;
using System.Collections.Generic;
using System.Text;

namespace daSyntaxAnalyser.Analysers.DfmAnalyser
{
    using daBase.Analyser;
    using daBase.Objects;
    using daBase.Objects.Safe;
    
    /// <summary>
    /// Синтаксический анализатор dfm-файлов
    /// </summary>
    [AnalyserDefaultSettings("Синтаксический анализатор dfm-файлов", "Неожиданная последовательность токенов!", 100, "Анализатор не в состоянии распознать данную символьную последовательность. Возможно она некорректна или анализатор работает неправильно. Сообщите о данной ошибке разработчику.")]
    public class DfmSyntaxAnalyser : LayerAnalyser
    {
        // шаблон "одиночка"
        private static DfmSyntaxAnalyser instance;

        /// <summary>
        /// Единственный экземпляр класса анализатора
        /// </summary>
        public static DfmSyntaxAnalyser Instance
        {
            get
            {
                if (instance == null)
                    instance = new DfmSyntaxAnalyser();
                return instance;
            }
        }

        // текущий анализ
        private int current;
        private List<DfmTokenInfo> tokens;
        private List<string> result;

        /// <summary>
        /// Инициализация
        /// </summary>
        private void Initialize()
        {
            current = 0;
            if (result == null)
                result = new List<string>();
            else result.Clear();
        }

        /// <summary>
        /// Инициализация
        /// </summary>
        /// <param name="FileName">имя файла</param>
        /// <param name="Source">исходный код</param>
        /// <param name="MetaTags">мета-теги соответствующего pas/dpr-файла</param>
        private void Initialize(string FileName, string Source, SafeDictionary<string, string> MetaTags)
        {
            file = FileName;
            Initialize();
            tokens = DfmLexicalAnalyser.Instance.Translate(FileName, Source, MetaTags);
        }

        /// <summary>
        /// Сравнение двух токенов
        /// </summary>
        /// <returns>результат сравнения</returns>
        private bool MatchTokens(DfmTokens t1, DfmTokens t2)
        {
            return (t1 == t2);
        }

        /// <summary>
        /// Сравшение токена с последним считанным
        /// </summary>
        /// <param name="t">токен, сравниваемые с последним</param>
        /// <returns>результат сравнения</returns>
        private bool MatchTokens(DfmTokens t)
        {
            return MatchTokens(tokens[current].Token, t);
        }

        /// <summary>
        /// Считывание очередного токена с заглядыванием назад
        /// </summary>
        /// <param name="t">предыдущий токен</param>
        private void Match(DfmTokens t)
        {
            if (MatchTokens(t)) current++;
            else Panic();
        }

        /// <summary>
        /// "Длинный" идентификатор
        /// </summary>
        private void LongId()
        {
            Match(DfmTokens.Id);
            while (MatchTokens(DfmTokens.Dot))
            {
                Match(DfmTokens.Dot);
                Match(DfmTokens.Id);
            }
        }

        /// <summary>
        /// Возможная строка
        /// </summary>
        private void OptionalString()
        {
            while (true)
            {
                switch (tokens[current].Token)
                {
                    case DfmTokens.String:
                    case DfmTokens.Char:
                        Match(tokens[current].Token);
                        break;
                    default:
                        return;
                }
            }
        }

        /// <summary>
        /// Строка
        /// </summary>
        private void String()
        {
            switch (tokens[current].Token)
            {
                case DfmTokens.String:
                case DfmTokens.Char:
                    OptionalString();
                    break;
                default:
                    Panic();
                    break;
            }
        }

        /// <summary>
        /// Строки
        /// </summary>
        private void Strings()
        {
            String();
            while (MatchTokens(DfmTokens.Addition))
            {
                Match(DfmTokens.Addition);
                String();
            }
        }

        /// <summary>
        /// Знаковое число
        /// </summary>
        private void SignedReal()
        {
            while (true)
            {
                switch (tokens[current].Token)
                {
                    case DfmTokens.Addition:
                    case DfmTokens.Subtruction:
                        Match(tokens[current].Token);
                        break;
                    case DfmTokens.HexConstant:
                        Match(DfmTokens.HexConstant);
                        return;
                    default:
                        Match(DfmTokens.Digits);
                        if (MatchTokens(DfmTokens.Dot))
                        {
                            Match(DfmTokens.Dot);
                            if (MatchTokens(DfmTokens.Digits))
                                Match(DfmTokens.Digits);
                        }
                        return;
                }
            }
        }

        /// <summary>
        /// Возможен набор идентификаторов
        /// </summary>
        private void OptionalIdList()
        {
            if (MatchTokens(DfmTokens.Id))
            {
                Match(DfmTokens.Id);
                while (MatchTokens(DfmTokens.Comma))
                {
                    Match(DfmTokens.Comma);
                    Match(DfmTokens.Id);
                }
            }
        }

        /// <summary>
        /// Возможен набор элементов
        /// </summary>
        private void OptionalElements()
        {
            while (true)
            {
                switch (tokens[current].Token)
                {
                    case DfmTokens.Id:
                    case DfmTokens.LBr:
                    case DfmTokens.LSBr:
                    case DfmTokens.LTBr:
                    case DfmTokens.LFBr:
                    case DfmTokens.String:
                    case DfmTokens.Char:
                    case DfmTokens.Addition:
                    case DfmTokens.Subtruction:
                    case DfmTokens.Digits:
                        Value();
                        break;
                    default:
                        return;
                }
            }
        }

        /// <summary>
        /// Значение свойства
        /// </summary>
        private void Value()
        {
            switch (tokens[current].Token)
            {
                case DfmTokens.Id:
                    LongId();
                    break;
                case DfmTokens.LBr:
                    Match(DfmTokens.LBr);
                    OptionalElements();
                    Match(DfmTokens.RBr);
                    break;
                case DfmTokens.LSBr:
                    Match(DfmTokens.LSBr);
                    OptionalIdList();
                    Match(DfmTokens.RSBr);
                    break;
                case DfmTokens.LTBr:
                    Match(DfmTokens.LTBr);
                    OptionalItems();
                    Match(DfmTokens.RTBr);
                    break;
                case DfmTokens.LFBr:
                    Match(DfmTokens.LFBr);
                    while (MatchTokens(DfmTokens.Digits) || MatchTokens(DfmTokens.Id))
                        Match(tokens[current].Token);
                    Match(DfmTokens.RFBr);
                    break;
                case DfmTokens.Char:
                case DfmTokens.String:
                    Strings();
                    break;
                default:
                    SignedReal();
                    break;
            }
        }

        /// <summary>
        /// Присвоение свойству значения
        /// </summary>
        private void Assigment()
        {
            LongId();
            Match(DfmTokens.Assigment);
            Value();
        }

        /// <summary>
        /// Элемент
        /// </summary>
        private void Item()
        {
            Match(DfmTokens.Item);
            OptionalFields();
            Match(DfmTokens.End);
        }

        /// <summary>
        /// Возможные элементы
        /// </summary>
        private void OptionalItems()
        {
            while (MatchTokens(DfmTokens.Item))
                Item();
        }

        /// <summary>
        /// Возможные поля
        /// </summary>
        private void OptionalFields()
        {
            while (true)
            {
                switch (tokens[current].Token)
                {
                    case DfmTokens.Object:
                    case DfmTokens.Inherited:
                    case DfmTokens.Inline:
                        Dfm();
                        break;
                    case DfmTokens.Id:
                        Assigment();
                        break;
                    default:
                        return;
                }
            }
        }

        /// <summary>
        /// Возможный индексатор
        /// </summary>
        private void OptionalIndexer()
        {
            if (MatchTokens(DfmTokens.LSBr))
            {
                Match(DfmTokens.LSBr);
                Match(DfmTokens.Digits);
                Match(DfmTokens.RSBr);
            }
        }

        /// <summary>
        /// Dfm файл
        /// </summary>
        private void Dfm()
        {
            switch (tokens[current].Token)
            {
                case DfmTokens.Object:
                case DfmTokens.Inherited:
                case DfmTokens.Inline:
                    Match(tokens[current].Token);
                    result.Add(tokens[current].Text.ToLower());
                    break;
                default:
                    Panic();
                    break;
            }
            Match(DfmTokens.Id);
            if (MatchTokens(DfmTokens.Colon))
            {
                Match(DfmTokens.Colon);
                result.Add(tokens[current].Text.ToLower());
                Match(DfmTokens.Id);
                OptionalIndexer();
                OptionalFields();
            }
            Match(DfmTokens.End);
        }

        /// <summary>
        /// Выборка участка кода, содержащего ошибку
        /// </summary>
        /// <returns></returns>
        private string GetCode()
        {
            int first = current - 6,
                last = current + 6;
            if (first < 0)
            {
                last -= first;
                first = 0;
            }
            if (last >= tokens.Count)
            {
                first -= last - tokens.Count + 1;
                last = tokens.Count - 1;
                if (first < 0) first = 0;
            }
            string result = string.Empty;
            for (int i = first; i <= last; i++)
                result += string.Format("{0} ", tokens[i].Text);
            return result;
        }

        /// <summary>
        /// Трансляция dfm-файла
        /// </summary>
        /// <param name="FileName">имя dfm-файла</param>
        /// <param name="Source">исходный код файла</param>
        /// <param name="MetaTags">мета-теги соответствующего pas/dpr-файла</param>
        /// <returns>список идентификаторов в файле</returns>
        public List<string> Translate(string FileName, string Source, SafeDictionary<string, string> MetaTags)
        {
            try
            {
                Initialize(FileName, Source, MetaTags);
                Match(DfmTokens.Undefined);
                Dfm();
                return result;
            }
            catch (Exception e)
            {
                if (e is DanalyserError)
                    AddError(tokens[current].Line, GetCode(), MetaTags);
                else AddUndefinedError(file, tokens[current].Line, GetCode(), new SafeDictionary<string, string>(s => string.Empty));
            }
            return result;
        }
    }
}
